//
// Created by hunan on 24-8-20.
//

#include "udp_socket_connection.h"
#include "socket_errno.h"
#include "socket_address.h"
#include "socket_channel.h"
#include "socket_description.h"
#include "./../description_module/description_module.h"
#include "./../write_read_module/buffer.h"
#include "./../description_module/description_util.h"
#include <arpa/inet.h>
#include <functional>
#include <memory>
#include <netinet/in.h>
#include <sys/socket.h>

class UdpSocketChannel final : public SocketChannel
{
public:
    UdpSocketChannel(Loop * loop, const std::shared_ptr<Description> & description, UdpSocketConnection * connection);
    ~UdpSocketChannel() override = default;
    void OnErrorEvent(std::shared_ptr<SocketChannel> channel, std::shared_ptr<Description> description) override;
    void OnReadEvent(std::shared_ptr<SocketChannel>  channel, std::shared_ptr<Description> description) override;
    void OnWriteEvent(std::shared_ptr<SocketChannel> channel, std::shared_ptr<Description> description) override;
private:
    UdpSocketConnection * m_connection;
};

UdpSocketChannel::UdpSocketChannel(Loop * loop, const std::shared_ptr<Description> & description, UdpSocketConnection * connection):SocketChannel(loop, description)
{
    m_connection = connection;
}

void UdpSocketChannel::OnErrorEvent(std::shared_ptr<SocketChannel> channel, std::shared_ptr<Description> description)
{

}

void UdpSocketChannel::OnReadEvent(std::shared_ptr<SocketChannel> channel, std::shared_ptr<Description> description)
{
    printf("description (%d) trigger read event.\n", description->DescriptionData());
    if (m_connection->ConnectState() == SocketConnection::EmConnectStateConnected)
    {
        WriteReadBuffer buffer(1500);
        struct sockaddr_in sai = {};
        socklen_t sai_len = sizeof(sai);
        auto result = ::recvfrom(description->DescriptionData(), buffer.BufferEnd(), buffer.BufferFreeSize(),0 , reinterpret_cast<struct sockaddr *>(&sai), &sai_len);
        if (result > 0)
        {
            printf("description %d recv message data size : %ld.\n",  description->DescriptionData(), result);
            SocketAddress address = SocketAddress::Address(sai);
            SocketConnection::Message message = {.m_data = buffer.BufferData(), .m_data_length = buffer.BufferDataSize(), .m_use_length = 0};
            auto cmd = SocketCommand::CreateCommand(SocketCommand::EmCmdRead, m_connection->shared_from_this(), message);
            m_connection->Update(&cmd);
        }
        int event = EmIoIn | EmIoErr | EmIoRdHup | EmIoOneShot;
        if (!m_connection->m_write_buffer_map.empty())
        {
            event |= EmIoOut;
        }
        channel->ModEvent(event);
    }
}

void UdpSocketChannel::OnWriteEvent(std::shared_ptr<SocketChannel> channel, std::shared_ptr<Description> description)
{
    printf("description (%d) trigger write event.\n", description->DescriptionData());
    if (m_connection->ConnectState() == SocketConnection::EmConnectStateConnected)
    {
        int need_continue_write = 0;
        for (auto iter = m_connection->m_write_buffer_map.begin(); iter != m_connection->m_write_buffer_map.end(); )
        {
            auto & item = *iter;
            SocketAddress address = SocketAddress::Address(item.first);
            auto addr = address.NetAddress();
            int result = sendto(description->DescriptionData(), item.second.BufferData(), item.second.BufferDataSize(), 0, (struct sockaddr*)&addr, sizeof(addr));
            if (result > 0)
            {
                item.second.RemoveSize(result);
                printf("description %d send message to %s data size : %d.\n",  description->DescriptionData(), item.first.c_str(), result);
            }
            else
            {
                printf("description (%d) sendto to %s ret : %d.\n", description->DescriptionData(), item.first.c_str(), result);
            }
            if (item.second.BufferDataSize() > 0)
            {
                need_continue_write = 1;
                ++iter;
            }
            else
            {
                iter = m_connection->m_write_buffer_map.erase(iter);
            }
        }
        int event = EmIoErr | EmIoRdHup | EmIoOneShot | EmIoIn;
        if (need_continue_write)
        {
            event |= EmIoOut;
        }
        channel->ModEvent(event);
    }
}

UdpSocketConnection::UdpSocketConnection(Loop * loop):SocketConnection(EmConnectionTypeUdp)
{
    m_channel = nullptr;
    m_description = nullptr;
    m_loop = loop;
}

int UdpSocketConnection::OpenSocket(SocketAddress address, Option option)
{
    m_description = std::make_shared<SocketDescription>(SocketDescription::UDPSOCKET);
    m_description->CreateDescription();
    m_channel = std::make_shared<UdpSocketChannel>(m_loop, m_description, this);
    if (address.PortToNumber() > 0 || !address.HostName().empty())
    {
        // 这里绑定信息
        struct ::sockaddr_in sai = {};
        sai.sin_port = htons(address.PortToNumber());
        if (address.HostName().empty())
        {
            inet_pton(AF_INET, address.HostName().c_str(), &sai.sin_addr.s_addr);
        }
        else
        {
            sai.sin_addr.s_addr = INADDR_ANY;
        }
        int result = bind(m_description->DescriptionData(), (struct ::sockaddr *)&sai, sizeof sai);
        if (result < 0)
        {
            return EmSocketErrno_BIND_ADDR_FAILED;
        }
    }
    if (option.m_enable_nonblock)
    {
        DescriptionUtil::DescriptionBlockStatus(m_channel->DescriptionData(), 0);
    }
    UpdateConnectState(EmConnectStateConnected);
    printf("description %d open success and state update to : %d.\n", m_description->DescriptionData(), ConnectState());
    return EmSocketErrno_SUCCESS;
}

int UdpSocketConnection::AsyncConnect(SocketAddress address, Option option)
{
    (void)this;
    return EmSocketErrno_SUCCESS;
}

int UdpSocketConnection::AsyncWrite(const uint8_t*, int)
{
    return EmSocketErrno_SUCCESS;
}

int UdpSocketConnection::AsyncRead()
{
    if (ConnectState() == EmConnectStateConnected)
    {
        auto self = shared_from_this();
        m_channel->Post([self]() {
            int event = self->m_write_buffer_map.empty() ? 0 : EmIoOut;
            if (self->m_channel->Index() > 0)
            {
                self->m_channel->ModEvent(EmIoRdHup | EmIoOneShot | EmIoIn | event);
            }
            else
            {
                self->m_channel->AddEvent(EmIoRdHup | EmIoOneShot | EmIoIn | event);
            }
        });
        return EmSocketErrno_SUCCESS;
    }
    return EmSocketErrno_READ_FAILED;
}

int UdpSocketConnection::AsyncWrite(const uint8_t * data, int length, SocketAddress & address)
{
    if (ConnectState() == EmConnectStateConnected)
    {
        std::string key = address.AddressWithPortKey();
        {
            m_write_buffer_map[key].PushDataToBuffer(data, length);
        }
        auto self = shared_from_this();
        m_channel->Post([self]() {

            if (self->m_channel->Index() > 0)
            {
                self->m_channel->ModEvent(EmIoErr | EmIoRdHup | EmIoOneShot | EmIoOut | EmIoIn);
            }
            else
            {
                self->m_channel->AddEvent(EmIoErr | EmIoRdHup | EmIoOneShot | EmIoOut | EmIoIn);
            }
        });
        return EmSocketErrno_SUCCESS;
    }
    return EmSocketErrno_SEND_FAILED;
}

void UdpSocketConnection::Close()
{
    UpdateConnectState(EmConnectStateDisconnected);
    if (m_channel)
    {
        auto self = shared_from_this();
        auto channel = m_channel;
        m_channel = nullptr;
        channel->Post([channel, self] {
            if (channel->Index() > 0)
            {
                channel->DelEvent();
            }
            if (self->m_description)
            {
                self->m_description->CloseDescription();
                self->m_description = nullptr;
            }
        });
    }
}

